<?php

/**
 * Archivo que describe la clase Template
 * @author Alejandro Gama <alejandro.gama@gmail.com>
 * @version 1.0
 * @package oak
 * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
 */
namespace org\copperforest;
/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

/**
 * Description of HTMLParse
 *
 * @author agama
 */
class HTMLParser
{

    private static $entities = array( 'quot', 'amp', 'apos', 'lt', 'gt', 'nbsp',
        'iexcl', 'cent', 'pound', 'curren', 'yen'
        , 'brvbar', 'sect', 'uml', 'copy', 'ordf', 'laquo', 'not', 'shy', 'reg',
        'macr', 'deg'
        , 'plusmn', 'sup2', 'sup3', 'acute', 'micro', 'para', 'middot', 'cedil',
        'sup1', 'ordm'
        , 'raquo', 'frac14', 'frac12', 'frac34', 'iquest', 'Agrave', 'Aacute', 'Acirc',
        'Atilde'
        , 'Auml', 'Aring', 'AElig', 'Ccedil', 'Egrave', 'Eacute', 'Ecirc', 'Euml',
        'Igrave'
        , 'Iacute', 'Icirc', 'Iuml', 'ETH', 'Ntilde', 'Ograve', 'Oacute', 'Ocirc',
        'Otilde', 'Ouml'
        , 'times', 'Oslash', 'Ugrave', 'Uacute', 'Ucirc', 'Uuml', 'Yacute', 'THORN',
        'szlig'
        , 'agrave', 'aacute', 'acirc', 'atilde', 'auml', 'aring', 'aelig', 'ccedil',
        'egrave'
        , 'eacute', 'ecirc', 'euml', 'igrave', 'iacute', 'icirc', 'iuml', 'eth',
        'ntilde', 'ograve'
        , 'oacute', 'ocirc', 'otilde', 'ouml', 'divide', 'oslash', 'ugrave', 'uacute',
        'ucirc'
        , 'uuml', 'yacute', 'thorn', 'yuml', 'OElig', 'oelig', 'Scaron', 'scaron',
        'Yuml', 'fnof'
        , 'circ', 'tilde', 'Alpha', 'Beta', 'Gamma', 'Delta', 'Epsilon', 'Zeta',
        'Eta', 'Theta'
        , 'Iota', 'Kappa', 'Lambda', 'Mu', 'Nu', 'Xi', 'Omicron', 'Pi', 'Rho', 'Sigma',
        'Tau'
        , 'Upsilon', 'Phi', 'Chi', 'Psi', 'Omega', 'alpha', 'beta', 'gamma', 'delta',
        'epsilon'
        , 'zeta', 'eta', 'theta', 'iota', 'kappa', 'lambda', 'mu', 'nu', 'xi', 'omicron',
        'pi', 'rho'
        , 'sigmaf', 'sigma', 'tau', 'upsilon', 'phi', 'chi', 'psi', 'omega', 'thetasym',
        'upsih'
        , 'piv', 'ensp', 'emsp', 'thinsp', 'zwnj', 'zwj', 'lrm', 'rlm', 'ndash',
        'mdash', 'lsquo'
        , 'rsquo', 'sbquo', 'ldquo', 'rdquo', 'bdquo', 'dagger', 'Dagger', 'bull',
        'hellip'
        , 'permil', 'prime', 'Prime', 'lsaquo', 'rsaquo', 'oline', 'frasl', 'euro',
        'image', 'weierp'
        , 'real', 'trade', 'alefsym', 'larr', 'uarr', 'rarr', 'darr', 'harr', 'crarr',
        'lArr', 'uArr'
        , 'rArr', 'dArr', 'hArr', 'forall', 'part', 'exist', 'empty', 'nabla', 'isin',
        'notin', 'ni'
        , 'prod', 'sum', 'minus', 'lowast', 'radic', 'prop', 'infin', 'ang', 'and',
        'or', 'cap', 'cup'
        , 'int', 'there4', 'sim', 'cong', 'asymp', 'ne', 'equiv', 'le', 'ge', 'sub',
        'sup', 'nsub'
        , 'sube', 'supe', 'oplus', 'otimes', 'perp', 'sdot', 'lceil', 'rceil', 'lfloor',
        'rfloor'
        , 'lang', 'rang', 'loz', 'spades', 'clubs', 'hearts', 'diams' );

    private static function parseAmpersAnd( $html )
    {

        $pilaTrozos = array( ); //para cada contenido que extraiga parte se tiene que poner el primer trozo y parte en el segundo
        $trozosHTML = array( '', $html );
        $pos_ini = 0;

        while ( $pos_ini < strlen( $trozosHTML[ 1 ] ) && ( $pos_ini = strpos( $trozosHTML[ 1 ],
        '&', $pos_ini ) ) !== false ) {


            if ( ( $pos_fin = strpos( $trozosHTML[ 1 ], ';', $pos_ini + 1 ) ) !== false ) {

                $interior = substr( $trozosHTML[ 1 ], $pos_ini + 1,
                        $pos_fin - $pos_ini - 1 );

                if ( in_array( $interior, self::$entities ) || preg_match( '/^#[0-9]{2,4}$/',
                                $interior ) || preg_match( '/^#x[0-9a-f]{2,4}$/',
                                strtolower( $interior ) ) ) {

                    $pilaTrozos[ ] = array( '', htmlspecialchars( substr( $trozosHTML[ 1 ],
                                        0, $pos_ini ) ) . '&' . $interior . ';' );

                    $trozosHTML[ 1 ] = substr( $trozosHTML[ 1 ], $pos_fin + 1 );
                    $pos_ini = 0;
                }
                else $pos_ini++;
            }
            else break;
        }

        //  $trozosHTML[0] = htmlspecialchars( $trozosHTML[0] );
        $trozosHTML[ 1 ] = htmlspecialchars( $trozosHTML[ 1 ] );

        while ( $trozos = array_pop( $pilaTrozos ) ) {
            //    $trozosHTML[0]=$trozosHTML[0].$trozos[0];
            $trozosHTML[ 1 ] = $trozos[ 1 ] . $trozosHTML[ 1 ];
        }

        return $trozosHTML[ 0 ] . $trozosHTML[ 1 ];
    }

    private static function parseAndEraseJavascript( $string )
    {
        $internoValidado = '';

        $coincidencias = array( );
        if ( preg_match_all( '/([a-z]+)\s*=\s*"([^"]+)"/', $string,
                        $coincidencias ) ) { //todo lo que no pase la expresion regular lo quitamos
            $num = count( $coincidencias[ 0 ] );

            for ( $i = 0; $i < $num; $i++ )
                    if ( substr( $coincidencias[ 1 ][ $i ], 0, 2 ) != 'on' && substr( ltrim( $coincidencias[ 2 ][ $i ] ),
                                0, 10 ) != 'javascript' )
                        $internoValidado.=' ' . $coincidencias[ 1 ][ $i ] . '="' . $coincidencias[ 2 ][ $i ] . '"';
        }

        $coincidencias = array( );
        if ( preg_match_all( '/([a-z]+)\s*=\s*\'([^\']+)\'/', $string,
                        $coincidencias ) ) { //todo lo que no pase la expresion regular lo quitamos
            $num = count( $coincidencias[ 0 ] );

            for ( $i = 0; $i < $num; $i++ )
                    if ( substr( $coincidencias[ 1 ][ $i ], 0, 2 ) != 'on' && substr( ltrim( $coincidencias[ 2 ][ $i ] ),
                                0, 10 ) != 'javascript' )
                        $internoValidado.=' ' . $coincidencias[ 1 ][ $i ] . '=\'' . $coincidencias[ 2 ][ $i ] . '\'';
        }

        return $internoValidado;
    }

    private static function parseAmpersAndSelfClosed( $html, $tags )
    {
        $pilaTrozos = array( ); //para cada contenido que extraiga parte se tiene que poner el primer trozo y parte en el segundo
        $trozosHTML = array( '', $html );
        $pos_fin = 0;

        while ( $pos_fin < strlen( $trozosHTML[ 1 ] ) && ( $pos_fin = strpos( $trozosHTML[ 1 ],
        '/>', $pos_fin ) ) !== false ) {

            $pos_tmp = 0;
            $pos_ini = false;

            while ( ( $pos_tmp = strpos( $trozosHTML[ 1 ], '<', $pos_tmp + 1 ) ) !== false && $pos_tmp < $pos_fin )
                $pos_ini = $pos_tmp;

            if ( $pos_ini !== false ) {

                $pos_mid = strpos( $trozosHTML[ 1 ], ' ', $pos_ini );

                if ( $pos_mid === false ) $pos_mid = $pos_fin;

                $etiqueta = rtrim( substr( $trozosHTML[ 1 ], $pos_ini + 1,
                                $pos_mid - $pos_ini - 1 ) );

                if ( in_array( $etiqueta, $tags ) ) {

                    if ( $pos_mid != $pos_fin )
                            $contenidoActual = '<' . $etiqueta . ' ' . self::parseAndEraseJavascript( substr( $trozosHTML[ 1 ],
                                                $pos_mid,
                                                $pos_fin - $pos_mid - 1 ) ) . ' />';
                    else $contenidoActual = '<' . $etiqueta . ' />';


                    $pilaTrozos[ ] = array( '', self::parseAmpersAnd( substr( $trozosHTML[ 1 ],
                                        0, $pos_ini ) ) . $contenidoActual );

                    $trozosHTML[ 1 ] = substr( $trozosHTML[ 1 ], $pos_fin + 2 );
                    $pos_fin = 0;
                }
                else $pos_fin+=2;
            }
            else break;
        }

        //  $trozosHTML[0] = htmlspecialchars( $trozosHTML[0] );
        $trozosHTML[ 1 ] = self::parseAmpersAnd( $trozosHTML[ 1 ] );

        while ( $trozos = array_pop( $pilaTrozos ) ) {
            //    $trozosHTML[0]=$trozosHTML[0].$trozos[0];
            $trozosHTML[ 1 ] = $trozos[ 1 ] . $trozosHTML[ 1 ];
        }

        return $trozosHTML[ 0 ] . $trozosHTML[ 1 ];
    }

    private static function findStartTag( $etiqueta, $cadena,
            $pos_fin_busqueda = null )
    {

        if ( $pos_fin_busqueda === null )
                $pos_fin_busqueda = strlen( $cadena ) - 1;

        $longitud = strlen( $etiqueta );

        //Ahora tengo que buscar la etiqueta de inicio o en la cadena fin o en el cadena inicio
        // $pos_fin_busqueda = $pos_ini_etiq_fin;

        while ( $pos_fin_busqueda !== false ) {

            $pos_ini_etiq_ini = false;

            //echo 'Buscando apertura pos_fin at '.$pos_fin.'<br />'."\r\n";
            $pos_tmp = $longitud * -1;

            while ( ( $pos_tmp = strpos( $cadena, '<' . $etiqueta,
            $pos_tmp + $longitud ) ) !== false && $pos_tmp < $pos_fin_busqueda )
                $pos_ini_etiq_ini = $pos_tmp;

            $pos_fin_busqueda = $pos_ini_etiq_ini;

            if ( $pos_ini_etiq_ini !== false ) {

                $pos_mid_etiq_ini = $pos_ini_etiq_ini + $longitud + 1;

                $pos_fin_etiq_ini = strpos( $cadena, '>', $pos_mid_etiq_ini );

                //?puedo asegurar q es esta?: dentro de una etiqueta no debería haber
                if ( $pos_fin_etiq_ini !== false && substr( $cadena,
                                $pos_fin_etiq_ini - 1, 1 ) != '/' )
                        return array( $pos_ini_etiq_ini, $pos_fin_etiq_ini );
            }
        }

        return false;
    }

    public static function parse( $html, $tags = array( ) )
    {
        $maxProfundidad = 0;
        $trozos = array( $html );

        $pos_ini_etiq_fin = 0;
        $lon_cad_fin = strlen( $trozos[ $maxProfundidad ] );


        //Los cierres de las etiquetas siempre estarán en $cad_fin
        while ( $pos_ini_etiq_fin < $lon_cad_fin && ( $pos_ini_etiq_fin = strpos( $trozos[ $maxProfundidad ],
        '</', $pos_ini_etiq_fin ) ) !== false ) {
            // echo 'Encontrado posible cierre  at '.$pos_ini_etiq_fin.'<br />'."\r\n";

            if ( ( $pos_fin_etiq_fin = strpos( $trozos[ $maxProfundidad ], '>',
                    $pos_ini_etiq_fin + 2 ) ) !== false ) {

                $etiqueta = rtrim( substr( $trozos[ $maxProfundidad ],
                                $pos_ini_etiq_fin + 2,
                                $pos_fin_etiq_fin - $pos_ini_etiq_fin - 2 ) );

                if ( in_array( $etiqueta, $tags ) ) {


                    $longitud = strlen( $etiqueta );

                    //busco en el ultimo trozo
                    $cord = self::findStartTag( $etiqueta,
                                    $trozos[ $maxProfundidad ],
                                    $pos_ini_etiq_fin ); //: esto solo en la ultima profundidad

                    if ( $cord !== false ) {

                        $trozos[ $maxProfundidad + 2 ] = substr( $trozos[ $maxProfundidad ],
                                $pos_fin_etiq_fin + 1 );

                        $trozos[ $maxProfundidad + 1 ] =
                                '<' . $etiqueta . ' ' . self::parseAndEraseJavascript( substr( $trozos[ $maxProfundidad ],
                                                $cord[ 0 ] + $longitud + 1,
                                                $cord[ 1 ] - $cord[ 0 ] - $longitud - 1 ) ) . '>' .
                                self::parseAmpersAndSelfClosed( substr( $trozos[ $maxProfundidad ],
                                                $cord[ 1 ] + 1,
                                                $pos_ini_etiq_fin - $cord[ 1 ] - 1 ),
                                        $tags ) .
                                '</' . $etiqueta . '>';

                        $trozos[ $maxProfundidad ] = substr( $trozos[ $maxProfundidad ],
                                0, $cord[ 0 ] );

                        $maxProfundidad += 2;
                        $lon_cad_fin = strlen( $trozos[ $maxProfundidad ] );
                        $pos_ini_etiq_fin = 0;
                    } else {

                        $profundidad = $maxProfundidad - 2;

                        while ( $cord == false && $profundidad > -1 ) {
                            //ahora o busco la etiqueta de inicio en maxProfundidad o en maxProfundidad -2

                            $cord = self::findStartTag( $etiqueta,
                                            $trozos[ $profundidad ] );

                            if ( $cord !== false ) {
                                //esto genera más trozos
                                //entre $maxProfundidad y $profundidad tengo que empaquetar los trozos

                                $trozos_new = array( );

                                for ( $i = 0; $i < $profundidad; $i++ )
                                        $trozos_new[ $i ] = $trozos[ $i ];

                                $trozos_new[ $profundidad ] = substr( $trozos[ $profundidad ],
                                        0, $cord[ 0 ] );

                                $interior = '';
                                $parseado = true;
                                for ( $i = $profundidad + 1;
                                            $i < $maxProfundidad; $i++ ) {
                                    if ( $parseado ) $interior .= $trozos[ $i ];
                                    else
                                            $interior .= self::parseAmpersAndSelfClosed( $trozos[ $i ],
                                                        $tags );

                                    $parseado = !$parseado;
                                }

                                $trozos_new[ $profundidad + 1 ] =
                                        '<' . $etiqueta . ' ' . self::parseAndEraseJavascript( substr( $trozos[ $profundidad ],
                                                        $cord[ 0 ] + $longitud + 1,
                                                        $cord[ 1 ] - $cord[ 0 ] - $longitud - 1 ) ) . '>' .
                                        self::parseAmpersAndSelfClosed( substr( $trozos[ $profundidad ],
                                                        $cord[ 1 ] + 1 ), $tags ) .
                                        $interior .
                                        self::parseAmpersAndSelfClosed( substr( $trozos[ $maxProfundidad ],
                                                        0, $pos_ini_etiq_fin ),
                                                $tags ) .
                                        '</' . $etiqueta . '>';

                                $trozos_new [ $profundidad + 2 ] = substr( $trozos[ $maxProfundidad ],
                                        $pos_fin_etiq_fin + 1 );

                                $trozos = $trozos_new;
                                unset( $trozos_new, $interior );

                                $maxProfundidad = $profundidad + 2;
                                $lon_cad_fin = strlen( $trozos[ $maxProfundidad ] );
                                $pos_ini_etiq_fin = 0;
                            }
                            else $profundidad -= 2;
                        }
                    }
                }
            }
        }

        $interior = '';
        $parseado = false;
        for ( $i = 0; $i < $maxProfundidad + 1; $i++ ) {
            if ( $parseado ) $interior .= $trozos[ $i ];
            else
                    $interior .= self::parseAmpersAndSelfClosed( $trozos[ $i ],
                                $tags );

            $parseado = !$parseado;
        }

        return $interior;
    }

}
